#include "zos_config.h"


#ifdef BOARD_USING_I2C_SHT3X


#include <string.h>
#include "sht3x.h"



#define CRC_POLY 0x31 

/**
 * This function write 2-byte cmd to SHT3x 
 *
 * @param dev the pointer of device driver structure
 * @param cmd the 16bit command to be sent to SHT3x
 *
 * @return the cmd transfer status, ZOS_EOK reprensents setting successfully.
 */
static zos_err_t write_cmd(sht3x_device_t dev, zos_uint16_t cmd)
{
    zos_uint8_t buf[2] ;
    buf[0] = cmd >> 8 ;
    buf[1] = cmd & 0xFF ;
	return zos_i2c_write(dev->i2c_bus,dev->sht3x_addr,buf,2);

}

/**
 *  This function read len bytes from dev and store the bytes in to buf 
 * 
 * @param dev the pointer of device driver structure
 * @param buf the pointer to buffer array 
 * @param len the number of bytes to be read 
 * 
 * @return the i2c read status, ZOS_EOK represents success.
 */
static zos_err_t read_bytes(sht3x_device_t dev, zos_uint8_t * buf, zos_uint8_t len)
{
	return zos_i2c_read(dev->i2c_bus,dev->sht3x_addr,buf,len);   
}

/**
 * This function calculate CRC value of bytes in buffer
 * CRC_POLY is predefined as 0x31
 * 
 * @param buf the pointer to buffer array 
 * @param len the length of buffer array 
 * 
 * @return calculated CRC value.
 */
static zos_uint8_t crc8(zos_uint8_t * buf, zos_uint8_t len)
{
    zos_uint8_t crc = 0xFF ;
    zos_uint8_t i, j ;
    for(j = len; j; j --){
        crc ^= *buf ++ ;
        for( i = 8; i; i --){
            crc = (crc & 0x80) ? (crc << 1) ^ CRC_POLY : (crc << 1) ;
        }
    }
    return crc ;
}

/** 
 * This function read temperature and humidity by single shot mode 
 * Attention: 
 *  - rt_thread_mdelay() is called to wait for SHT3x to be ready to read
 *  - the temperature and humidity is stored in the device driver structure
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the status of read data from SHT3x, ZOS_EOK means success.
 */
zos_err_t sht3x_read_singleshot(sht3x_device_t dev)
{
    zos_uint8_t temp[6] ;
    zos_err_t result;
    ZOS_ASSERT(dev);

    result = zos_mutex_lock(dev->lock, ZOS_WAITING_FOREVER);
    if (result == ZOS_EOK)
    {
        zos_task_delay(10);
        if( write_cmd(dev, dev->cmd_readout) == ZOS_EOK)
        {
			// wait about 5 ms
			zos_task_delay(5);
            if(read_bytes(dev, temp, 6) == ZOS_EOK){
                if(crc8(temp, 2) == temp[2]){
                    dev->temperature = -45.0 + (temp[1] | temp[0] << 8) * 175.0 / (0xFFFF - 1) ; //sensor temperature convert to reality
                    result = ZOS_EOK ;
                }else{
                    result = ZOS_ERROR ;
                }
                if(crc8(temp + 3, 2) == temp[5]){
                    dev->humidity = (temp[4] | temp[3] << 8) * 0.0015259022 ; //sensor humidity convert to reality
                }else{
                    result = ZOS_ERROR ;
                }
            }else
            {
                result = ZOS_ERROR ;
            }
        }
        else
        {
            result = ZOS_ERROR ;
        }
	zos_mutex_unlock(dev->lock);
    }
    else
    {
        zos_printf("Taking mutex of SHT3x failed.\r\n");
        result = ZOS_ERROR;
    }
	
    return result;
}

/**
 * This function resets all parameter with default
 *
 * @param dev the pointer of device driver structure
 *
 * @return the softreset status, ZOS_EOK reprensents setting successfully.
 */
zos_err_t sht3x_softreset(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);
    
    if( write_cmd(dev, CMD_SOFT_RESET) == ZOS_EOK)
    {
        return ZOS_EOK;
    }else{
        return ZOS_ERROR ;
    }
}

/** 
 * This function clear the status register in SHT3x  
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_clear_status(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);

    if( write_cmd(dev, CMD_CLEAR_STATUS) == ZOS_EOK)
    {
        return ZOS_EOK ;
    }else{
        return ZOS_ERROR ;
    }
}

/**
 * This function read the status register from SHT3x
 * Attention:
 *  - the status word is stored in device driver structure
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_read_status(sht3x_device_t dev)
{
    zos_uint8_t buf[3];
    ZOS_ASSERT(dev);
    if( write_cmd(dev, CMD_READ_STATUS) == ZOS_EOK)
    {
        read_bytes(dev, buf, 3);
        if( crc8(buf, 2) == buf[2])
        {
            dev->status.status_word = ( buf[0] << 8 ) | buf[1];
            return ZOS_EOK ;
        }
    }

    return ZOS_ERROR ;
}

/**
 * This function enable heater
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_enable_heater(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);
    if( write_cmd(dev, CMD_HEATER_ENABLE) == ZOS_EOK)
    {
        return ZOS_EOK ;
    }else
    {
        return ZOS_ERROR ;
    }
}

/**
 * This function disable heater
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_disable_heater(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);
    if( write_cmd(dev, CMD_HEATER_DISABLE) == ZOS_EOK)
    {
        return ZOS_EOK ;
    }else
    {
        return ZOS_ERROR ;
    }
}

/**
 * This function write accelerated response time command to SHT3x
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_acc_resp_time(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);
    if( write_cmd(dev, CMD_ART) == ZOS_EOK)
    {
        return ZOS_EOK ;
    }else
    {
        return ZOS_ERROR ;
    }
}

/**
 * This function write break command to SHT3x to break out of continuous readout mode
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, ZOS_EOK means success.
 */
zos_err_t sht3x_break(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);
    if( write_cmd(dev, CMD_BREAK) == ZOS_EOK)
    {
        return ZOS_EOK ;
    }else
    {
        return ZOS_ERROR ;
    }
}

/**
 * This function initializes sht3x registered device driver
 *
 * @param i2c_bus_name the name of i2c device used by SHT3x device
 * @param sht3x_addr the address of SHT3x device
 * 
 * @return the pointer to sht3x device driver structure.
 */
sht3x_device_t sht3x_init(ZOS_I2C_BUS i2c_bus, zos_uint8_t sht3x_addr)
{
    sht3x_device_t dev;
	ZOS_I2C_CONFIG i2c_cfg;

    dev = zos_malloc(sizeof(struct sht3x_device));
    if (dev == ZOS_NULL)
    {
        zos_printf("Can't allocate memory for sht3x device on '%d' \r\n", i2c_bus);
        return ZOS_NULL;
    }
    dev->i2c_bus=i2c_bus;
	memset(dev,0,sizeof(struct sht3x_device));
    if(sht3x_addr == SHT3X_ADDR_PD || sht3x_addr == SHT3X_ADDR_PU){
        dev->sht3x_addr = sht3x_addr ;
        dev->i2c_bus=i2c_bus;
    }else{
        zos_printf("Illegal sht3x address:'%x'\r\n", sht3x_addr);
        zos_free(dev);
        return ZOS_NULL;
    }
	i2c_cfg.mode=ZOS_I2C_MODE_MASTER;
	i2c_cfg.speed=ZOS_I2C_SPEED_400K;
    if(zos_i2c_init(dev->i2c_bus,&i2c_cfg) == ZOS_ERROR)
	{
		zos_printf("Illegal sht3x address:'%x'\r\n", sht3x_addr);
        zos_free(dev);
        return ZOS_NULL;
	}
    dev->lock = zos_mutex_create(ZOS_IPC_FLAG_FIFO);
    if (dev->lock == ZOS_NULL)
    {
        zos_printf("Can't create mutex for sht3x device on '%s' \r\n", dev->i2c_bus);
        zos_free(dev);
        return ZOS_NULL;
    }
    // I2C streching disabled, medium repeatability for default single shot readout
    dev->cmd_readout = CMD_MEAS_POLLING_M ;

	// clear the status register
	sht3x_clear_status(dev);
	
    return dev;
}

/**
 * This function releases memory and deletes mutex lock
 *
 * @param dev the pointer of device driver structure
 */
void sht3x_deinit(sht3x_device_t dev)
{
    ZOS_ASSERT(dev);

    zos_mutex_delete(dev->lock);

    zos_free(dev);
}

#endif
